package blockworld;

//import jade.util.leap.HashMap;

import java.util.logging.Logger;

import eis.iilang.Percept;
import grid.items.Body;

import apapl.Environment;
import apapl.ExternalActionFailedException;
import apapl.data.APLFunction;
import apapl.data.APLIdent;
import apapl.data.APLList;
import apapl.data.APLListVar;
import apapl.data.APLNum;
import apapl.data.APLVar;
import apapl.data.Term;

//import base_classes.Client;

import java.awt.Point;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.omg.Dynamic.Parameter;



public class Env extends Environment
{
	
	private Logger _logger = Logger.getLogger("Blockworld."+Env.class.getName());
	
	HashMap<String, World> _worlds = new HashMap<String, World>();
	
	public Env()
	{
		super();
		
		Client.setEnv( this );
		
		System.out.println( "Agent numbers: " + Client.getNrAgents() );
		System.out.println( "Team name: " + Client.getTeamName() );
		System.out.println( "Placement: " + Client.getPlacement());
		System.out.println( "Width of World: " + Client.getWidth());
		System.out.println( "Height of World: " + Client.getHeight() );
		//		try {
//		Thread.sleep( 5000 );
//		} catch( Exception e ){};
	
	}
	
	public void printWorlds()
	{
		
		for(Entry<String,World> entry : _worlds.entrySet())
		{
			System.out.println("-------------WORLD FOR AGENT" + entry.getKey() + " -----");
			System.out.println(entry.getValue());
			System.out.println("-------------WORLD FOR AGENT" + entry.getKey() + " -----");
		}
	
	}
	
	/**
	 * Returns the properties of the world. (Width,Height,NrOfAgents and Placement)
	 * @param sAgent
	 * @return A list containing the properties of the world. (width,height,NrOfAgents and placement of the world.
	 * @throws ExternalActionFailedException
	 */
	public Term getProperties(String sAgent) throws ExternalActionFailedException
	{

		LinkedList<Term> result = new LinkedList<Term>();
		//width(X),heigth(Y), nrAgents(Z), placement(Q)
		APLNum width = new APLNum(Client.getWidth());
		APLNum height = new APLNum(Client.getHeight());
		APLNum nrAgents = new APLNum(Client.getNrAgents());
		APLIdent teamName = new APLIdent(Client.getTeamName());
		APLIdent placement = new APLIdent(Direction.toString(Client.getPlacement()));

		
		result.add(width);
		result.add(height);
		result.add(nrAgents);
		result.add(teamName);
		result.add(placement);
		
		
		
		APLList list = new APLList(result);
		
//		System.out.println(list.toString());
		
		return list;
	}
	
	
	public Term abs(String sAgent, APLNum aplValue) throws ExternalActionFailedException
	{
		int value = aplValue.toInt();
		int absValue = Math.abs(value);
		return new APLList(new APLNum(absValue));
	}
	
	
	public Term getMaxYOfTeamMates(String sAgent)
	{
		World world = null;
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		int maxY = 0;
		
		List<Placement> teamMates = world.getTeamMembers();
		for(Placement teamMate : teamMates)
		{
			if(teamMate.getY() > maxY)
				maxY = teamMate.getY();
		}
		
		return wrapNumber(maxY);
	}
	
	public Term getMaxXOfTeamMates(String sAgent)
	{
		World world = null;
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		int maxX = 0;
		
		List<Placement> teamMates = world.getTeamMembers();
		for(Placement teamMate : teamMates)
		{
			if(teamMate.getX() > maxX)
				maxX = teamMate.getX();
		}
		
		return wrapNumber(maxX);
	}
	
	public Term getMinYOfTeamMates(String sAgent)
	{
		World world = null;
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		int minY = Integer.MAX_VALUE;
		
		List<Placement> teamMates = world.getTeamMembers();
		for(Placement teamMate : teamMates)
		{
			if(teamMate.getY() < minY)
				minY = teamMate.getY();
		}
		
		if(minY == Integer.MAX_VALUE)
			minY = 0;
		
		return wrapNumber(minY);
	}
	
	public Term getMinXOfTeamMates(String sAgent)
	{
		World world = null;
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		int minX = Integer.MAX_VALUE;
		
		List<Placement> teamMates = world.getTeamMembers();
		for(Placement teamMate : teamMates)
		{
			if(teamMate.getX() < minX)
				minX = teamMate.getX();
		}
		
		if(minX == Integer.MAX_VALUE)
			minX = 0;
		
		return wrapNumber(minX);
	}
	
	
	/**
	 * Places the agents in a logical world, and returns their positions.
	 * @param sAgent The name of the caller agent, must be manager.
	 * @return A list of the placement(Agent,X,Y,BodyType).
	 * @throws ExternalActionFailedException
	 */
	public Term placeAgents(String sAgent, APLList agentNames, APLNum width, APLNum height, APLNum nrAgents, APLIdent teamName, APLIdent placement) throws ExternalActionFailedException
	{
		String[] agentNamesList = unwrapStringArray(agentNames);	
		List<Placement> placements = World.placeAgents(width.toInt(), height.toInt(), nrAgents.toInt(), Direction.fromString(placement.getName()),teamName.getName(),agentNamesList);
		return wrapPlacements(placements);
	}
	
	public Term getEnemiesInShootingRange(String sAgent) throws ExternalActionFailedException
	{
		World world = null;
		
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		///
		List<Placement> enemies = world.getEnemiesInShootingRange();
		return wrapPlacements(enemies);
	}
	
	/**
	 * Get the enemies in the Sense range
	 * @param sAgent
	 * @return
	 * @throws ExternalActionFailedException
	 */
	public Term getEnemies(String sAgent) throws ExternalActionFailedException
	{
		World world = null;
		
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		List<Placement> enemies = world.getEnemies();
		return wrapPlacements(enemies);
	}
	
	
	public Term getTeamMembers(String sAgent) throws ExternalActionFailedException
	{
		World world = null;
		if(_worlds.containsKey(sAgent))
		{
			world = _worlds.get(sAgent);
		}
		else
		{
			throw new Error();
		}
		
		List<Placement> teamMates = world.getTeamMembers();
		return wrapPlacements(teamMates);
	}
	
	public Term enter(String sAgent, APLNum aplX, APLNum aplY, APLIdent aplBodyType, APLNum width, APLNum height, APLNum nrAgents, APLIdent teamName, APLIdent placement) throws ExternalActionFailedException
	{
		String bodyString = aplBodyType.getName();
		int bodyType = Body.PAPER;
		
		if(bodyString.equals("rock"))
			bodyType = Body.ROCK;
		else if(bodyString.equals("paper"))
			bodyType = Body.PAPER;
		else if(bodyString.equals("scissor"))
			bodyType = Body.SCISSOR;
		
		int x = aplX.toInt();
		int y = aplY.toInt();
		
		Client.enter( sAgent, bodyType, x, y );
		
		
		if(!_worlds.containsKey(sAgent))
		{
			EBody ebody = new EBody(sAgent, teamName.getName(), bodyType);
			World world = new World(width.toInt(), height.toInt(), nrAgents.toInt(), ebody, new Point(x,y));
			_worlds.put(sAgent, world);
		}	
		
		return wrapBoolean( true );
	}
	
	public Term moveTo(String sAgent, APLNum aplX, APLNum aplY, APLIdent aplDirection) throws ExternalActionFailedException
	{
		int direction = Direction.fromString(aplDirection.getName());	
		Point pos = new Point(aplX.toInt(), aplY.toInt());
		Client.move(sAgent, pos, direction);
		return wrapBoolean( true );
	}
	
	
	public Term noMove(String sAgent) throws ExternalActionFailedException
	{
		Client.noMove(sAgent);
		
		return wrapBoolean(true);
	}
	
	
	
	public Term registerShots(String sAgent, APLList aplShots)
	{
		
		
		
		try
		{
			World world = null;
		
			if(_worlds.containsKey(sAgent))
				world = _worlds.get(sAgent);
			else
				return wrapBoolean(false);
			
			
			
			
			LinkedList<Term> terms = aplShots.toLinkedList();
			for(Term term : terms)
			{
				
				System.out.println( "%%% RegisterShots %%% " + term.toString() );
				
				APLList aplSublist = (APLList)term;
				
			
				
				APLNum aplX = (APLNum)aplSublist.toLinkedList().get(0);		
				APLNum aplY = (APLNum)aplSublist.toLinkedList().get(1);
				APLNum aplDamage = (APLNum)aplSublist.toLinkedList().get(2);
				APLIdent aplName = (APLIdent)aplSublist.toLinkedList().get(3);
				APLIdent aplTeam = (APLIdent)aplSublist.toLinkedList().get(4);
				APLIdent aplType = (APLIdent)aplSublist.toLinkedList().get(5);
				
				int x = aplX.toInt();
				int y = aplY.toInt();
				int damage = aplDamage.toInt();
				String name = aplName.toString();
				String team = aplTeam.toString();
				int type = EBody.typeFromString( aplType.toString() );
				
				world.registerShot(x, y, damage, name, team, type);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		return wrapBoolean(true);
	}
	
	
	public Term registerShot(String sAgent, APLNum aplX, APLNum aplY, APLNum aplDamage, APLIdent aplTargetName, APLIdent aplTargetTeam, APLIdent aplTargetType )
	{
		System.out.println("+++ +++ Registering shot from " + sAgent + "to: " + aplX.toInt() + ","+ aplY.toInt() + ", damage: " + aplDamage.toInt());
		
		
		
		if(_worlds.containsKey(sAgent))
			_worlds.get(sAgent).registerShot(aplX.toInt(), aplY.toInt(), aplDamage.toInt(), aplTargetName.toString(), aplTargetTeam.toString(), EBody.typeFromString( aplTargetType.toString() ) );
		
		return wrapBoolean(true);
	}

	
	public Term shoot(String sAgent, APLNum aplX, APLNum aplY) throws ExternalActionFailedException
	{
		System.out.println("+++ +++ Shooting from " + sAgent + "to: " + aplX.toInt() + ","+ aplY.toInt());
		
		if(_worlds.containsKey(sAgent))
			_worlds.get(sAgent).shoot(aplX.toInt(), aplY.toInt());
		
		Client.shoot(sAgent, aplX.toInt(), aplY.toInt());
		
		return wrapBoolean( true );
	}
	
	public Term noShoot(String sAgent) throws ExternalActionFailedException
	{
		
		System.out.println("+++ +++ NO SHOOT from " + sAgent + "");
		
		Client.noShoot(sAgent);
		
	
		
		return wrapBoolean( true );
	}
	
	public Term notifyMe( String sAgent ) throws ExternalActionFailedException 
	{ 
		try {
			
			APLFunction aplFunction = new APLFunction("cowsInCorral", new APLNum(1), new APLNum(2));
			
			throwEvent(aplFunction, sAgent);
			
		//notifyAgents( new Percept( "cowsInCorral", 1, 2 ), sAgent );
		} catch (Exception e){}
		
		return wrapBoolean( true );
	}
	
	public void gameStarted(String[] agents)
	{
		try
		{		
			APLFunction gameStarted = new APLFunction("gamestarted", new APLIdent("yes"));
			throwEvent(gameStarted, agents);
		}
		catch(Exception e)
		{
			e.printStackTrace();
			System.exit(-1);
		}
			
	}
	
	public void died(String forAgent)
	{
		try
		{
			
			APLFunction agentDiedFunction = new APLFunction("died",new APLIdent("yes"));
			throwEvent(agentDiedFunction, forAgent);
		}
		catch(Exception e)
		{
			e.printStackTrace();
			System.exit(-1);
		}
		_worlds.remove(forAgent);
	}
	
	//state == afterShoot OR state == afterMove
	public void sensed(String stateForAgent, String forAgent, Point agentPosition, Map<Body, Point> agents )
	{
//		String listOfBodies
		try 
		{
			if(_worlds.containsKey(forAgent))
			{
				World world = _worlds.get(forAgent);
				world.processSense(agentPosition, agents);
			}	
			
			
			APLNum thisAgentX = new APLNum(agentPosition.x);
			APLNum thisAgentY = new APLNum(agentPosition.y);
			
	
			LinkedList<Term> sensedAgents = new LinkedList<Term>();
			for(Entry<Body,Point> entry : agents.entrySet())
			{
				Body b = entry.getKey();
				Point p = entry.getValue();
				
				if(b.getName().equals(forAgent))
				{
					//
					//dont need information about myself, allready have it.
				}
				else
				{
					LinkedList<Term> agentProperteis = new LinkedList<Term>();
					agentProperteis.add(new APLIdent(b.getName()));
					agentProperteis.add(new APLIdent(b.getTeam()));
					agentProperteis.add(new APLNum(b.getType()));
					agentProperteis.add(new APLNum(p.x));
					agentProperteis.add(new APLNum(p.y));
					APLList aplAgentPropperties = new APLList(agentProperteis);
					sensedAgents.add(aplAgentPropperties);
				}
			}
			APLList aplSensedAgents = new APLList(sensedAgents);
			
			APLIdent state = new APLIdent(stateForAgent);
			
			APLFunction aplFunction = new APLFunction("sensed", state, thisAgentX, thisAgentY , aplSensedAgents);
			throwEvent(aplFunction, forAgent);
			
		} catch (Exception e )
		{
			e.printStackTrace();
			System.exit(-1);
		}	
	}
	
	public static String[] unwrapStringArray(APLList list)
	{
		String[] array = new String[list.toLinkedList().size()];
		int i=0;
		for(Term t : list.toLinkedList())
		{
			array[i] = t.toString();
			i++;
		}
		return array;
	}
	
	public static APLList wrapPlacements(List<Placement> placements)
	{
		LinkedList<Term> list = new LinkedList<Term>();
		for(Placement placement : placements)
		{
			APLIdent name = new APLIdent( placement.getEBody().getName() );
			APLIdent team = new APLIdent(placement.getEBody().getTeam() );
			APLNum hp = new APLNum(placement.getEBody().getExpectedHP());
			APLNum x = new APLNum( placement.getX() );
			APLNum y = new APLNum( placement.getY() );			
			APLIdent bodyType = new APLIdent( placement.getEBody().getTypeString());
			APLList sublist = new APLList(x, y, name,team, bodyType,hp);
			list.add(sublist);
		}
		return new APLList(list);
	}
	
	 /* Helper functions */
	static public APLList wrapBoolean( boolean b )
	{
		return new APLList(new APLIdent(b ? "true" : "false"));
	}

	public static APLList wrapNumber(int x)
	{
		return new APLList(new APLNum(x));
	}
	
	
    public void throwExternalActionFailed (String message ) throws ExternalActionFailedException
    {
    	_logger.info( message );
    	throw new ExternalActionFailedException( message );
    }
    
    public static void main(String[] args)
    {
    	try
    	{
    		Env env = new Env();   	
    		env.getProperties("s");
    	}
    	catch(Exception e)
    	{
    		e.printStackTrace();
    	}
    }
    
}